﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using AMS.SubCipherSolver.Controllers;
using AMS.SubCipherSolver.Args;

namespace AMS.SubCipherSolver.Views
{
    public partial class SolverForm : Form
    {
        SolverController _controller;
        Dictionary<ComboBox, char> _ddlsToChars;

        public bool LettersValid
        {
            get { return cbLetters.Checked; }
            set { cbLetters.Checked = value; }
        }

        public bool DigitsValid
        {
            get { return cbDigits.Checked; }
            set { cbDigits.Checked = value; }
        }

        public bool PunctuationValid
        {
            get { return cbPunctuation.Checked; }
            set { cbPunctuation.Checked = value; }
        }

        public SolverForm(SolverController controller)
        {
            InitializeComponent();
            this._controller = controller;
            this._ddlsToChars = new Dictionary<ComboBox, char>();
            LoadDictionary();

            this._controller.CipherUpdated += new EventHandler<CipherEventArgs>(_controller_CipherUpdated);
            this._controller.SolutionSaved += new EventHandler(_controller_SolutionSaved);
            this._controller.KeySaved += new EventHandler(_controller_KeySaved);
            this._controller.SolutionUpdated += new EventHandler<SolutionEventArgs>(_controller_SolutionUpdated);
            this._controller.CipherAnalyzedForCharsInWordsCount += new EventHandler<CipherEventArgs>(_controller_CipherAnalyzedForCharsInWordsCount);
            this._controller.CipherAnalyzedForDoubleChars += new EventHandler<CipherEventArgs>(_controller_CipherAnalyzedForDoubleChars);
            this._controller.CipherAnalyzedForCommonWordPatterns += new EventHandler<CipherPatternEventArgs>(_controller_CipherAnalyzedForCommonWordPatterns);
            this._controller.KeyLoaded += new EventHandler<KeyLoadedEventArgs>(_controller_KeyLoaded);
            this._controller.CipherAnalyzedForShortWords += new EventHandler<Args.CipherWordsEventArgs>(_controller_CipherAnalyzedForShortWords);
        }

        private void ResetDDLsEnabled(IEnumerable<char> cipherChars)
        {
            foreach (ComboBox cb in _ddlsToChars.Keys)
            {
                cb.Enabled = cipherChars.Contains(_ddlsToChars[cb]);
            }
        }

        bool reloading;
        private void ReloadDDLs()
        {
            reloading = true;
            List<char> validChars = new List<char>(GenerateValidChars());
            foreach (ComboBox cb in _ddlsToChars.Keys)
            {
                // using same array makes all change when one changed
                cb.DataSource = validChars.ToArray();
                cb.SelectedIndex = 0;
            }
            reloading = false;
        }

        private char[] GenerateValidChars()
        {
            int count = 1;
            count += LettersValid ? 26 : 0;
            count += DigitsValid ? 10 : 0;
            count += PunctuationValid ? 12 : 0;
            char[] validChars = new char[count];

            validChars[0] = '_';
            int i = 1;
            if (LettersValid)
            {
                for (char c = 'A'; c <= 'Z'; c++, i++)
                    validChars[i] = c;
            }
            if (DigitsValid)
            {
                for (char c = '0'; c <= '9'; c++, i++)
                    validChars[i] = c;
            }
            if (PunctuationValid)
            {
                validChars[i++] = '.';
                validChars[i++] = ',';
                validChars[i++] = '!';
                validChars[i++] = '?';
                validChars[i++] = '\'';
                validChars[i++] = '(';
                validChars[i++] = ')';
                validChars[i++] = '%';
                validChars[i++] = '$';
                validChars[i++] = '@';
                validChars[i++] = '"';
                validChars[i++] = '#';
            }
            return validChars;
        }

        private void LoadDictionary()
        {
            this._ddlsToChars.Add(ddl0, '0');
            this._ddlsToChars.Add(ddl1, '1');
            this._ddlsToChars.Add(ddl2, '2');
            this._ddlsToChars.Add(ddl3, '3');
            this._ddlsToChars.Add(ddl4, '4');
            this._ddlsToChars.Add(ddl5, '5');
            this._ddlsToChars.Add(ddl6, '6');
            this._ddlsToChars.Add(ddl7, '7');
            this._ddlsToChars.Add(ddl8, '8');
            this._ddlsToChars.Add(ddl9, '9');

            this._ddlsToChars.Add(ddlA, 'a');
            this._ddlsToChars.Add(ddlB, 'b');
            this._ddlsToChars.Add(ddlC, 'c');
            this._ddlsToChars.Add(ddlD, 'd');
            this._ddlsToChars.Add(ddlE, 'e');
            this._ddlsToChars.Add(ddlF, 'f');
            this._ddlsToChars.Add(ddlG, 'g');
            this._ddlsToChars.Add(ddlH, 'h');
            this._ddlsToChars.Add(ddlI, 'i');
            this._ddlsToChars.Add(ddlJ, 'j');
            this._ddlsToChars.Add(ddlK, 'k');
            this._ddlsToChars.Add(ddlL, 'l');
            this._ddlsToChars.Add(ddlM, 'm');
            this._ddlsToChars.Add(ddlN, 'n');
            this._ddlsToChars.Add(ddlO, 'o');
            this._ddlsToChars.Add(ddlP, 'p');
            this._ddlsToChars.Add(ddlQ, 'q');
            this._ddlsToChars.Add(ddlR, 'r');
            this._ddlsToChars.Add(ddlS, 's');
            this._ddlsToChars.Add(ddlT, 't');
            this._ddlsToChars.Add(ddlU, 'u');
            this._ddlsToChars.Add(ddlV, 'v');
            this._ddlsToChars.Add(ddlW, 'w');
            this._ddlsToChars.Add(ddlX, 'x');
            this._ddlsToChars.Add(ddlY, 'y');
            this._ddlsToChars.Add(ddlZ, 'z');

            this._ddlsToChars.Add(ddlPeriod, '.');
            this._ddlsToChars.Add(ddlComma, ',');
            this._ddlsToChars.Add(ddlExclamation, '!');
            this._ddlsToChars.Add(ddlQuestion, '?');
            this._ddlsToChars.Add(ddlApostrophe, '\'');
            this._ddlsToChars.Add(ddlQuote, '"');
            this._ddlsToChars.Add(ddlParenOpen, '(');
            this._ddlsToChars.Add(ddlParenClose, ')');
            this._ddlsToChars.Add(ddlPercent, '%');
            this._ddlsToChars.Add(ddlPound, '#');
            this._ddlsToChars.Add(ddlDollar, '$');
            this._ddlsToChars.Add(ddlAt, '@');
        }
        
        private void CrunchCounts(Dictionary<char, int> cipherCharCounts, TextBox outputTo)
        {
            List<KeyValuePair<char, int>> charCounts = new List<KeyValuePair<char, int>>(cipherCharCounts);
            charCounts.Sort(new KeyValuePairComparer<char, int>(false));
            string text = String.Empty;
            foreach (KeyValuePair<char, int> kvp in charCounts)
            {
                text += String.Format("{0} - {1}\r\n", kvp.Key, kvp.Value);
            }
            if (text.Length > 1)
                text = text.Substring(0, text.Length - 2);
            outputTo.Text = text;
        }

        #region Form Event Handlers
        private void HandleDropDownListSelectedIndexChanged(object sender, EventArgs e)
        {
            if (reloading)
                return;

            ComboBox cb = sender as ComboBox;

            if (cb == null)
                return;

            if (_ddlsToChars.ContainsKey(cb))
                _controller.CharacterSelectionChanged(_ddlsToChars[cb], (char)cb.SelectedItem);
        }

        private void btnLoadCipher_Click(object sender, EventArgs e)
        {
            _controller.LoadCipher();
        }

        private void btnLoadKey_Click(object sender, EventArgs e)
        {
            _controller.LoadKey();
        }

        private void btnSaveKey_Click(object sender, EventArgs e)
        {
            _controller.SaveKey();
        }

        private void btnSaveSolution_Click(object sender, EventArgs e)
        {
            _controller.SaveSolution();
        }

        private void HandleCharsCheckedChanged(object sender, EventArgs e)
        {
            ReloadDDLs();
        } 
        #endregion

        #region ControllerEventHandlers
        private void _controller_CipherUpdated(object sender, CipherEventArgs e)
        {
            txtCipher.Text = e.Cipher;
            ResetDDLsEnabled(e.CipherCharCounts.Keys);
            CrunchCounts(e.CipherCharCounts, txtCharCounts);
        }

        private void _controller_SolutionUpdated(object sender, SolutionEventArgs e)
        {
            this.txtSolution.Text = e.Solution;
        }

        private void _controller_SolutionSaved(object sender, EventArgs e)
        {
            MessageBox.Show("Solution saved", "Success", MessageBoxButtons.OK, MessageBoxIcon.Information);
        }

        void _controller_KeySaved(object sender, EventArgs e)
        {
            MessageBox.Show("Key saved", "Success", MessageBoxButtons.OK, MessageBoxIcon.Information);
        }

        private void _controller_CipherAnalyzedForCharsInWordsCount(object sender, CipherEventArgs e)
        {
            CrunchCounts(e.CipherCharCounts, txtCharInWordCounts);
        }

        private void _controller_CipherAnalyzedForDoubleChars(object sender, CipherEventArgs e)
        {
            CrunchCounts(e.CipherCharCounts, txtDoubles);
        }

        private void _controller_CipherAnalyzedForCommonWordPatterns(object sender, CipherPatternEventArgs e)
        {
            string text = String.Empty;
            foreach (string pattern in e.Patterns.Keys)
            {
                foreach (char c in e.Patterns[pattern])
                    text += String.Format("{0} - {1}\n\r", pattern, c);
            }
            if (text.Length > 1)
                text = text.Substring(0, text.Length - 2);
            txtPatterns.Text = text;
        }

        private void _controller_KeyLoaded(object sender, KeyLoadedEventArgs e)
        {
            this.LettersValid = e.UseLetters;
            this.DigitsValid = e.UseDigits;
            this.PunctuationValid = e.UsePunctuation;
        }
        #endregion

        Dictionary<int, HashSet<string>> wordsByLength;
        private void _controller_CipherAnalyzedForShortWords(object sender, CipherWordsEventArgs e)
        {
            // TODO: populate degrees of lists
            // ddlCharCount, ddlWordOfThatLength, txtReplaceWord, btnReplace
            wordsByLength = e.WordsByLength;
            List<int> lengths = new List<int>(wordsByLength.Keys);
            lengths.Sort();
            ddlWordLength.DataSource = lengths.ToArray();
            pause = true;
            ddlWordLength.SelectedIndex = -1;
            pause = false;
        }

        bool pause;
        private void ddlWordLength_SelectedIndexChanged(object sender, EventArgs e)
        {
            if (pause)
                return;

            int i = (int)ddlWordLength.SelectedItem;
            ddlWords.DataSource = wordsByLength[i].ToArray();
            ddlWords.SelectedIndex = -1;
        }

        private void btnReplace_Click(object sender, EventArgs e)
        {
            string cipherWord = ddlWords.SelectedItem.ToString();
            string solnWord = txtReplace.Text.ToUpper();

            if (cipherWord.Length != solnWord.Length)
            {
                MessageBox.Show("words aren't the same length", "Error", MessageBoxButtons.OK,MessageBoxIcon.Error);
                return;
            }

            // TODO: change selected index of each applicable DDL
            for (int i = 0; i < cipherWord.Length; i++)
                _controller.CharacterSelectionChanged(cipherWord[i], solnWord[i]);
        }
    }
}
